// This code is derived from jcifs smb client library <jcifs at samba dot org>
// Ported by J. Arturo <webmaster at komodosoft dot net>
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using SharpCifs.Netbios;
using SharpCifs.Util;
using SharpCifs.Util.Sharpen;
using SharpCifs.Util.Transport;

namespace SharpCifs.Smb
{
    public class SmbTransport : Transport
    {
        internal static readonly byte[] Buf = new byte[0xFFFF];

        internal static readonly SmbComNegotiate NegotiateRequest = new SmbComNegotiate(
            );

        internal static LogStream LogStatic = LogStream.GetInstance();

        internal static Hashtable DfsRoots = null;


        internal static SmbTransport GetSmbTransport(UniAddress address, int port
            )
        {
            lock (typeof(SmbTransport))
            {
                return GetSmbTransport(address, port, SmbConstants.Laddr, SmbConstants.Lport, null);
            }
        }

        internal static SmbTransport GetSmbTransport(UniAddress address, int port
            , IPAddress localAddr, int localPort, string hostName)
        {
            lock (typeof(SmbTransport))
            {
                SmbTransport conn;

                lock (SmbConstants.Connections)
                {
                    if (SmbConstants.SsnLimit != 1)
                    {
                        conn =
                            SmbConstants.Connections.FirstOrDefault(
                                c =>
                                    c.Matches(address, port, localAddr, localPort, hostName) &&
                                    (SmbConstants.SsnLimit ==
                                     0 || c.Sessions.Count < SmbConstants.SsnLimit));

                        if (conn != null)
                        {
                            return conn;
                        }

                    }

                    conn = new SmbTransport(address, port, localAddr, localPort);
                    SmbConstants.Connections.Insert(0, conn);
                }
                return conn;
            }
        }

        internal class ServerData
        {
            internal byte Flags;

            internal int Flags2;

            internal int MaxMpxCount;

            internal int MaxBufferSize;

            internal int SessionKey;

            internal int Capabilities;

            internal string OemDomainName;

            internal int SecurityMode;

            internal int Security;

            internal bool EncryptedPasswords;

            internal bool SignaturesEnabled;

            internal bool SignaturesRequired;

            internal int MaxNumberVcs;

            internal int MaxRawSize;

            internal long ServerTime;

            internal int ServerTimeZone;

            internal int EncryptionKeyLength;

            internal byte[] EncryptionKey;

            internal byte[] Guid;

            internal ServerData(SmbTransport enclosing)
            {
                this._enclosing = enclosing;
            }

            private readonly SmbTransport _enclosing;
        }

        internal IPAddress LocalAddr;

        internal int LocalPort;

        internal UniAddress Address;

        internal SocketEx Socket;

        internal int Port;

        internal int Mid;

        internal OutputStream Out;

        internal InputStream In;

        internal byte[] Sbuf = new byte[512];

        internal SmbComBlankResponse Key = new SmbComBlankResponse();

        internal long SessionExpiration = Runtime.CurrentTimeMillis() + SmbConstants.SoTimeout;

        internal List<object> Referrals = new List<object>();

        internal SigningDigest Digest;

        internal List<SmbSession> Sessions = new List<SmbSession>();

        internal ServerData Server;

        internal int Flags2 = SmbConstants.Flags2;

        internal int MaxMpxCount = SmbConstants.MaxMpxCount;

        internal int SndBufSize = SmbConstants.SndBufSize;

        internal int RcvBufSize = SmbConstants.RcvBufSize;

        internal int Capabilities = SmbConstants.Capabilities;

        internal int SessionKey = 0x00000000;

        internal bool UseUnicode = SmbConstants.UseUnicode;

        internal string TconHostName;

        internal SmbTransport(UniAddress address, int port, IPAddress localAddr, int localPort
            )
        {
            Server = new ServerData(this);
            this.Address = address;
            this.Port = port;
            this.LocalAddr = localAddr;
            this.LocalPort = localPort;
        }

        internal virtual SmbSession GetSmbSession()
        {
            lock (this)
            {
                return GetSmbSession(new NtlmPasswordAuthentication(null, null, null));
            }
        }

        internal virtual SmbSession GetSmbSession(NtlmPasswordAuthentication auth)
        {
            lock (this)
            {
                SmbSession ssn;
                long now;

                ssn = Sessions.FirstOrDefault(s => s.Matches(auth));
                if (ssn != null)
                {
                    ssn.Auth = auth;
                    return ssn;
                }

                if (SmbConstants.SoTimeout > 0 && SessionExpiration < (now = Runtime.CurrentTimeMillis()))
                {
                    SessionExpiration = now + SmbConstants.SoTimeout;

                    foreach (var session in Sessions.Where(s => s.Expiration < now))
                    {
                        session.Logoff(false);
                    }
                }
                ssn = new SmbSession(Address, Port, LocalAddr, LocalPort, auth);
                ssn.transport = this;
                Sessions.Add(ssn);
                return ssn;
            }
        }

        internal virtual bool Matches(UniAddress address, int port, IPAddress localAddr,
            int localPort, string hostName)
        {
            if (hostName == null)
            {
                hostName = address.GetHostName();
            }
            return (TconHostName == null || Runtime.EqualsIgnoreCase(hostName, TconHostName)) && address.Equals(this.Address) && (port == -1 || port == this.Port
                 || (port == 445 && this.Port == 139)) && (localAddr == this.LocalAddr || (localAddr
                 != null && localAddr.Equals(this.LocalAddr))) && localPort == this.LocalPort;
        }

        /// <exception cref="SharpCifs.Smb.SmbException"></exception>
        internal virtual bool HasCapability(int cap)
        {
            try
            {
                Connect(SmbConstants.ResponseTimeout);
            }
            catch (IOException ioe)
            {
                throw new SmbException(ioe.Message, ioe);
            }
            return (Capabilities & cap) == cap;
        }

        internal virtual bool IsSignatureSetupRequired(NtlmPasswordAuthentication auth)
        {
            return (Flags2 & SmbConstants.Flags2SecuritySignatures) != 0 && Digest ==
                 null && auth != NtlmPasswordAuthentication.Null && NtlmPasswordAuthentication.Null
                .Equals(auth) == false;
        }

        /// <exception cref="System.IO.IOException"></exception>
        internal virtual void Ssn139()
        {
            Name calledName = new Name(Address.FirstCalledName(), 0x20, null
                );
            do
            {
                Socket = new SocketEx(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                if (LocalAddr != null)
                {
                    Socket.Bind2(new IPEndPoint(LocalAddr, LocalPort));
                }

                Socket.Connect(new IPEndPoint(IPAddress.Parse(Address.GetHostAddress()), 139), SmbConstants.ConnTimeout);
                Socket.SoTimeOut = SmbConstants.SoTimeout;

                Out = Socket.GetOutputStream();
                In = Socket.GetInputStream();
                SessionServicePacket ssp = new SessionRequestPacket(calledName, NbtAddress.GetLocalName
                    ());
                Out.Write(Sbuf, 0, ssp.WriteWireFormat(Sbuf, 0));
                if (Readn(In, Sbuf, 0, 4) < 4)
                {
                    try
                    {
                        Socket.Close();
                    }
                    catch (IOException)
                    {
                    }
                    throw new SmbException("EOF during NetBIOS session request");
                }
                switch (Sbuf[0] & 0xFF)
                {
                    case SessionServicePacket.PositiveSessionResponse:
                        {
                            if (Log.Level >= 4)
                            {
                                Log.WriteLine("session established ok with " + Address);
                            }
                            return;
                        }

                    case SessionServicePacket.NegativeSessionResponse:
                        {
                            int errorCode = In.Read() & 0xFF;
                            switch (errorCode)
                            {
                                case NbtException.CalledNotPresent:
                                case NbtException.NotListeningCalled:
                                    {
                                        Socket.Close();
                                        break;
                                    }

                                default:
                                    {
                                        Disconnect(true);
                                        throw new NbtException(NbtException.ErrSsnSrvc, errorCode);
                                    }
                            }
                            break;
                        }

                    case -1:
                        {
                            Disconnect(true);
                            throw new NbtException(NbtException.ErrSsnSrvc, NbtException.ConnectionRefused
                                );
                        }

                    default:
                        {
                            Disconnect(true);
                            throw new NbtException(NbtException.ErrSsnSrvc, 0);
                        }
                }
            }
            while ((calledName.name = Address.NextCalledName()) != null);
            throw new IOException("Failed to establish session with " + Address);
        }

        /// <exception cref="System.IO.IOException"></exception>
        private void Negotiate(int port, ServerMessageBlock resp)
        {
            lock (Sbuf)
            {
                if (port == 139)
                {
                    Ssn139();
                }
                else
                {
                    if (port == -1)
                    {
                        port = SmbConstants.DefaultPort;
                    }
                    // 445
                    Socket = new SocketEx(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                    if (LocalAddr != null)
                    {
                        Socket.Bind2(new IPEndPoint(LocalAddr, LocalPort));
                    }

                    Socket.Connect(new IPEndPoint(IPAddress.Parse(Address.GetHostAddress()), port), SmbConstants.ConnTimeout);
                    Socket.SoTimeOut = SmbConstants.SoTimeout;
                    Out = Socket.GetOutputStream();
                    In = Socket.GetInputStream();
                }
                if (++Mid == 32000)
                {
                    Mid = 1;
                }
                NegotiateRequest.Mid = Mid;
                int n = NegotiateRequest.Encode(Sbuf, 4);
                Encdec.Enc_uint32be(n & 0xFFFF, Sbuf, 0);
                if (Log.Level >= 4)
                {
                    Log.WriteLine(NegotiateRequest);
                    if (Log.Level >= 6)
                    {
                        Hexdump.ToHexdump(Log, Sbuf, 4, n);
                    }
                }
                Out.Write(Sbuf, 0, 4 + n);
                Out.Flush();
                if (PeekKey() == null)
                {
                    throw new IOException("transport closed in negotiate");
                }
                int size = Encdec.Dec_uint16be(Sbuf, 2) & 0xFFFF;
                if (size < 33 || (4 + size) > Sbuf.Length)
                {
                    throw new IOException("Invalid payload size: " + size);
                }
                Readn(In, Sbuf, 4 + 32, size - 32);
                resp.Decode(Sbuf, 4);
                if (Log.Level >= 4)
                {
                    Log.WriteLine(resp);
                    if (Log.Level >= 6)
                    {
                        Hexdump.ToHexdump(Log, Sbuf, 4, n);
                    }
                }
            }
        }

        /// <exception cref="SharpCifs.Smb.SmbException"></exception>
        public virtual void Connect()
        {
            try
            {
                base.Connect(SmbConstants.ResponseTimeout);
            }
            catch (TransportException te)
            {
                throw new SmbException("Failed to connect: " + Address, te);
            }
        }

        /// <exception cref="System.IO.IOException"></exception>
        protected internal override void DoConnect()
        {
            SmbComNegotiateResponse resp = new SmbComNegotiateResponse(Server);
            try
            {
                Negotiate(Port, resp);
            }
            catch (ConnectException)
            {
                Port = (Port == -1 || Port == SmbConstants.DefaultPort) ? 139 : SmbConstants.DefaultPort;
                Negotiate(Port, resp);
            }
            if (resp.DialectIndex > 10)
            {
                throw new SmbException("This client does not support the negotiated dialect.");
            }
            if ((Server.Capabilities & SmbConstants.CapExtendedSecurity) != SmbConstants.CapExtendedSecurity && Server
                .EncryptionKeyLength != 8 && SmbConstants.LmCompatibility == 0)
            {
                throw new SmbException("Unexpected encryption key length: " + Server.EncryptionKeyLength
                    );
            }
            TconHostName = Address.GetHostName();
            if (Server.SignaturesRequired || (Server.SignaturesEnabled && SmbConstants.Signpref))
            {
                Flags2 |= SmbConstants.Flags2SecuritySignatures;
            }
            else
            {
                Flags2 &= 0xFFFF ^ SmbConstants.Flags2SecuritySignatures;
            }
            MaxMpxCount = Math.Min(MaxMpxCount, Server.MaxMpxCount);
            if (MaxMpxCount < 1)
            {
                MaxMpxCount = 1;
            }
            SndBufSize = Math.Min(SndBufSize, Server.MaxBufferSize);
            Capabilities &= Server.Capabilities;
            if ((Server.Capabilities & SmbConstants.CapExtendedSecurity) == SmbConstants.CapExtendedSecurity)
            {
                Capabilities |= SmbConstants.CapExtendedSecurity;
            }
            // & doesn't copy high bit
            if ((Capabilities & SmbConstants.CapUnicode) == 0)
            {
                // server doesn't want unicode
                if (SmbConstants.ForceUnicode)
                {
                    Capabilities |= SmbConstants.CapUnicode;
                }
                else
                {
                    UseUnicode = false;
                    Flags2 &= 0xFFFF ^ SmbConstants.Flags2Unicode;
                }
            }
        }

        /// <exception cref="System.IO.IOException"></exception>
        protected internal override void DoDisconnect(bool hard)
        {
            try
            {
                foreach (var ssn in Sessions)
                {
                    ssn.Logoff(hard);
                }

                Out.Close();
                In.Close();
                Socket.Close();
            }
            finally
            {
                Digest = null;
                Socket = null;
                TconHostName = null;
            }

        }

        /// <exception cref="System.IO.IOException"></exception>
        protected internal override void MakeKey(ServerMessageBlock request)
        {
            if (++Mid == 32000)
            {
                Mid = 1;
            }
            request.Mid = Mid;
        }

        /// <exception cref="System.IO.IOException"></exception>
        protected internal override ServerMessageBlock PeekKey()
        {
            int n;
            do
            {
                if ((n = Readn(In, Sbuf, 0, 4)) < 4)
                {
                    return null;
                }
            }
            while (Sbuf[0] == 0x85);
            if ((n = Readn(In, Sbuf, 4, 32)) < 32)
            {
                return null;
            }
            if (Log.Level >= 4)
            {
                Log.WriteLine("New data read: " + this);
                Hexdump.ToHexdump(Log, Sbuf, 4, 32);
            }
            for (; ; )
            {
                if (Sbuf[0] == 0x00 && Sbuf[1] == 0x00 &&
                    Sbuf[4] == 0xFF &&
                    Sbuf[5] == 'S' &&
                    Sbuf[6] == 'M' &&
                    Sbuf[7] == 'B')
                {
                    break;
                }
                for (int i = 0; i < 35; i++)
                {
                    Sbuf[i] = Sbuf[i + 1];
                }
                int b;
                if ((b = In.Read()) == -1)
                {
                    return null;
                }
                Sbuf[35] = unchecked((byte)b);
            }
            Key.Mid = Encdec.Dec_uint16le(Sbuf, 34) & 0xFFFF;
            return Key;
        }

        /// <exception cref="System.IO.IOException"></exception>
        protected internal override void DoSend(ServerMessageBlock request)
        {
            lock (Buf)
            {
                ServerMessageBlock smb = request;
                int n = smb.Encode(Buf, 4);
                Encdec.Enc_uint32be(n & 0xFFFF, Buf, 0);
                if (Log.Level >= 4)
                {
                    do
                    {
                        Log.WriteLine(smb);
                    }
                    while (smb is AndXServerMessageBlock && (smb = ((AndXServerMessageBlock)smb).Andx
                        ) != null);
                    if (Log.Level >= 6)
                    {
                        Hexdump.ToHexdump(Log, Buf, 4, n);
                    }
                }
                Out.Write(Buf, 0, 4 + n);
            }
        }

        /// <exception cref="System.IO.IOException"></exception>
        protected internal virtual void DoSend0(ServerMessageBlock request)
        {
            try
            {
                DoSend(request);
            }
            catch (IOException ioe)
            {
                if (Log.Level > 2)
                {
                    Runtime.PrintStackTrace(ioe, Log);
                }
                try
                {
                    Disconnect(true);
                }
                catch (IOException ioe2)
                {
                    Runtime.PrintStackTrace(ioe2, Log);
                }
                throw;
            }
        }

        /// <exception cref="System.IO.IOException"></exception>
        protected internal override void DoRecv(Response response)
        {
            ServerMessageBlock resp = (ServerMessageBlock)response;
            resp.UseUnicode = UseUnicode;
            resp.ExtendedSecurity = (Capabilities & SmbConstants.CapExtendedSecurity) == SmbConstants.CapExtendedSecurity;
            lock (Buf)
            {
                Array.Copy(Sbuf, 0, Buf, 0, 4 + SmbConstants.HeaderLength);
                int size = Encdec.Dec_uint16be(Buf, 2) & 0xFFFF;
                if (size < (SmbConstants.HeaderLength + 1) || (4 + size) > RcvBufSize)
                {
                    throw new IOException("Invalid payload size: " + size);
                }
                int errorCode = Encdec.Dec_uint32le(Buf, 9) & unchecked((int)(0xFFFFFFFF));
                if (resp.Command == ServerMessageBlock.SmbComReadAndx && (errorCode == 0 || errorCode
                     == unchecked((int)(0x80000005))))
                {
                    // overflow indicator normal for pipe
                    SmbComReadAndXResponse r = (SmbComReadAndXResponse)resp;
                    int off = SmbConstants.HeaderLength;
                    Readn(In, Buf, 4 + off, 27);
                    off += 27;
                    resp.Decode(Buf, 4);
                    int pad = r.DataOffset - off;
                    if (r.ByteCount > 0 && pad > 0 && pad < 4)
                    {
                        Readn(In, Buf, 4 + off, pad);
                    }
                    if (r.DataLength > 0)
                    {
                        Readn(In, r.B, r.Off, r.DataLength);
                    }
                }
                else
                {
                    Readn(In, Buf, 4 + 32, size - 32);
                    resp.Decode(Buf, 4);
                    if (resp is SmbComTransactionResponse)
                    {
                        ((SmbComTransactionResponse)resp).Current();
                    }
                }
                if (Digest != null && resp.ErrorCode == 0)
                {
                    Digest.Verify(Buf, 4, resp);
                }
                if (Log.Level >= 4)
                {
                    Log.WriteLine(response);
                    if (Log.Level >= 6)
                    {
                        Hexdump.ToHexdump(Log, Buf, 4, size);
                    }
                }
            }
        }

        /// <exception cref="System.IO.IOException"></exception>
        protected internal override void DoSkip()
        {
            int size = Encdec.Dec_uint16be(Sbuf, 2) & 0xFFFF;
            if (size < 33 || (4 + size) > RcvBufSize)
            {
                In.Skip(In.Available());
            }
            else
            {
                In.Skip(size - 32);
            }
        }

        /// <exception cref="SharpCifs.Smb.SmbException"></exception>
        internal virtual void CheckStatus(ServerMessageBlock req, ServerMessageBlock resp
            )
        {
            resp.ErrorCode = SmbException.GetStatusByCode(resp.ErrorCode);
            switch (resp.ErrorCode)
            {
                case NtStatus.NtStatusOk:
                    {
                        break;
                    }

                case NtStatus.NtStatusAccessDenied:
                case NtStatus.NtStatusWrongPassword:
                case NtStatus.NtStatusLogonFailure:
                case NtStatus.NtStatusAccountRestriction:
                case NtStatus.NtStatusInvalidLogonHours:
                case NtStatus.NtStatusInvalidWorkstation:
                case NtStatus.NtStatusPasswordExpired:
                case NtStatus.NtStatusAccountDisabled:
                case NtStatus.NtStatusAccountLockedOut:
                case NtStatus.NtStatusTrustedDomainFailure:
                    {
                        throw new SmbAuthException(resp.ErrorCode);
                    }

                case NtStatus.NtStatusPathNotCovered:
                    {
                        if (req.Auth == null)
                        {
                            throw new SmbException(resp.ErrorCode, null);
                        }
                        DfsReferral dr = GetDfsReferrals(req.Auth, req.Path, 1);
                        if (dr == null)
                        {
                            throw new SmbException(resp.ErrorCode, null);
                        }
                        SmbFile.Dfs.Insert(req.Path, dr);
                        throw dr;
                    }

                case unchecked((int)(0x80000005)):
                    {
                        break;
                    }

                case NtStatus.NtStatusMoreProcessingRequired:
                    {
                        break;
                    }

                default:
                    {
                        throw new SmbException(resp.ErrorCode, null);
                    }
            }
            if (resp.VerifyFailed)
            {
                throw new SmbException("Signature verification failed.");
            }
        }

        /// <exception cref="SharpCifs.Smb.SmbException"></exception>
        internal virtual void Send(ServerMessageBlock request, ServerMessageBlock response
            )
        {
            Connect();
            request.Flags2 |= Flags2;
            request.UseUnicode = UseUnicode;
            request.Response = response;
            if (request.Digest == null)
            {
                request.Digest = Digest;
            }
            try
            {
                if (response == null)
                {
                    DoSend0(request);
                    return;
                }
                if (request is SmbComTransaction)
                {
                    response.Command = request.Command;
                    SmbComTransaction req = (SmbComTransaction)request;
                    SmbComTransactionResponse resp = (SmbComTransactionResponse)response;
                    req.MaxBufferSize = SndBufSize;
                    resp.Reset();
                    try
                    {
                        BufferCache.GetBuffers(req, resp);
                        req.Current();
                        if (req.MoveNext())
                        {
                            SmbComBlankResponse interim = new SmbComBlankResponse();
                            Sendrecv(req, interim, SmbConstants.ResponseTimeout);
                            if (interim.ErrorCode != 0)
                            {
                                CheckStatus(req, interim);
                            }
                            req.Current();
                        }
                        else
                        {
                            MakeKey(req);
                        }
                        lock (this)
                        {
                            response.Received = false;
                            resp.IsReceived = false;
                            try
                            {
                                ResponseMap.Put(req, resp);
                                do
                                {
                                    DoSend0(req);
                                }
                                while (req.MoveNext() && req.Current() != null);
                                long timeout = SmbConstants.ResponseTimeout;
                                resp.Expiration = Runtime.CurrentTimeMillis() + timeout;
                                while (resp.MoveNext())
                                {
                                    Runtime.Wait(this, timeout);
                                    timeout = resp.Expiration - Runtime.CurrentTimeMillis();
                                    if (timeout <= 0)
                                    {
                                        throw new TransportException(this + " timedout waiting for response to " + req);
                                    }
                                }
                                if (response.ErrorCode != 0)
                                {
                                    CheckStatus(req, resp);
                                }
                            }
                            catch (Exception ie)
                            {
                                if (ie is SmbException)
                                {
                                    throw;
                                }
                                else
                                {
                                    throw new TransportException(ie);                                    
                                }
                            }
                            finally
                            {
                                //Sharpen.Collections.Remove<Hashtable, SmbComTransaction>(response_map, req);
                                ResponseMap.Remove(req);
                            }
                        }
                    }
                    finally
                    {
                        BufferCache.ReleaseBuffer(req.TxnBuf);
                        BufferCache.ReleaseBuffer(resp.TxnBuf);
                    }
                }
                else
                {
                    response.Command = request.Command;
                    Sendrecv(request, response, SmbConstants.ResponseTimeout);
                }
            }
            catch (SmbException se)
            {
                throw;
            }
            catch (IOException ioe)
            {
                throw new SmbException(ioe.Message, ioe);
            }
            CheckStatus(request, response);
        }

        public override string ToString()
        {
            return base.ToString() + "[" + Address + ":" + Port + "]";
        }

        internal virtual void DfsPathSplit(string path, string[] result)
        {
            int ri = 0;
            int rlast = result.Length - 1;
            int i = 0;
            int b = 0;
            int len = path.Length;
            do
            {
                if (ri == rlast)
                {
                    result[rlast] = Runtime.Substring(path, b);
                    return;
                }
                if (i == len || path[i] == '\\')
                {
                    result[ri++] = Runtime.Substring(path, b, i);
                    b = i + 1;
                }
            }
            while (i++ < len);
            while (ri < result.Length)
            {
                result[ri++] = string.Empty;
            }
        }

        /// <exception cref="SharpCifs.Smb.SmbException"></exception>
        internal virtual DfsReferral GetDfsReferrals(NtlmPasswordAuthentication auth, string
             path, int rn)
        {
            SmbTree ipc = GetSmbSession(auth).GetSmbTree("IPC$", null);
            Trans2GetDfsReferralResponse resp = new Trans2GetDfsReferralResponse();
            ipc.Send(new Trans2GetDfsReferral(path), resp);
            if (resp.NumReferrals == 0)
            {
                return null;
            }
            if (rn == 0 || resp.NumReferrals < rn)
            {
                rn = resp.NumReferrals;
            }
            DfsReferral dr = new DfsReferral();
            string[] arr = new string[4];
            long expiration = Runtime.CurrentTimeMillis() + Dfs.Ttl * 1000;
            int di = 0;
            for (; ; )
            {
                dr.ResolveHashes = auth.HashesExternal;
                dr.Ttl = resp.Referrals[di].Ttl;
                dr.Expiration = expiration;
                if (path.Equals(string.Empty))
                {
                    dr.Server = Runtime.Substring(resp.Referrals[di].Path, 1).ToLower();
                }
                else
                {
                    DfsPathSplit(resp.Referrals[di].Node, arr);
                    dr.Server = arr[1];
                    dr.Share = arr[2];
                    dr.Path = arr[3];
                }
                dr.PathConsumed = resp.PathConsumed;
                di++;
                if (di == rn)
                {
                    break;
                }
                dr.Append(new DfsReferral());
                dr = dr.Next;
            }
            return dr.Next;
        }

        /// <exception cref="SharpCifs.Smb.SmbException"></exception>
        internal virtual DfsReferral[] __getDfsReferrals(NtlmPasswordAuthentication auth,
            string path, int rn)
        {
            SmbTree ipc = GetSmbSession(auth).GetSmbTree("IPC$", null);
            Trans2GetDfsReferralResponse resp = new Trans2GetDfsReferralResponse();
            ipc.Send(new Trans2GetDfsReferral(path), resp);
            if (rn == 0 || resp.NumReferrals < rn)
            {
                rn = resp.NumReferrals;
            }
            DfsReferral[] drs = new DfsReferral[rn];
            string[] arr = new string[4];
            long expiration = Runtime.CurrentTimeMillis() + Dfs.Ttl * 1000;
            for (int di = 0; di < drs.Length; di++)
            {
                DfsReferral dr = new DfsReferral();
                dr.ResolveHashes = auth.HashesExternal;
                dr.Ttl = resp.Referrals[di].Ttl;
                dr.Expiration = expiration;
                if (path.Equals(string.Empty))
                {
                    dr.Server = Runtime.Substring(resp.Referrals[di].Path, 1).ToLower();
                }
                else
                {
                    DfsPathSplit(resp.Referrals[di].Node, arr);
                    dr.Server = arr[1];
                    dr.Share = arr[2];
                    dr.Path = arr[3];
                }
                dr.PathConsumed = resp.PathConsumed;
                drs[di] = dr;
            }
            return drs;
        }
    }
}
